<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>
    fork系统调用分析 |
    
    无敌小笼包</title>
  
    <link rel="shortcut icon" href="/favicon.png">
  
  <link rel="stylesheet" href="/css/style.css">
  
    <link rel="stylesheet" href="/fancybox/jquery.fancybox.min.css">
  
  <script src="/js/pace.min.js"></script>
</head>

<body>
<main class="content">
  <section class="outer">
  <article id="post-fork系统调用分析" class="article article-type-post" itemscope itemprop="blogPost">

  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 class="article-title" itemprop="name">
      fork系统调用分析
    </h1>
  

      </header>
    

    
      <div class="article-meta">
        <a href="/2017/09/24/fork系统调用分析/" class="article-date">
  <time datetime="2017-09-24T04:47:24.000Z" itemprop="datePublished">2017-09-24</time>
</a>
        
  <div class="article-category">
    <a class="article-category-link" href="/categories/php/">php</a>
  </div>

      </div>
    

    <div class="article-entry" itemprop="articleBody">
      
      
      
        <p>在Linux中，主要是通过fork的方式产生新的进程，我们都知道每个进程都在 内核对应一个PCB块，内核通过对PCB块的操作做到对进程的管理。在Linux内核中，PCB对应着的结构体就是task_struct，也就是所谓的进程描述符（process descriptor）。该数据结构中包含了程相关的所有信息，比如包含众多描述进程属性的字段，以及指向其他与进程相关的结构体的指针。因此，进程描述符内部是比较复杂的。这个结构体的声明位于include/linux/sched.h中。</p>
<a id="more"></a>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在Linux中，主要是通过fork的方式产生新的进程，我们都知道每个进程都在 内核对应一个PCB块，内核通过对PCB块的操作做到对进程的管理。在Linux内核中，PCB对应着的结构体就是task_struct，也就是所谓的进程描述符（process descriptor）。该数据结构中包含了程相关的所有信息，比如包含众多描述进程属性的字段，以及指向其他与进程相关的结构体的指针。因此，进程描述符内部是比较复杂的。这个结构体的声明位于include/linux/sched.h中。</p>
<p>task_struct中有指向mm_struct结构体的指针mm，也有指向fs_struct结构体的指针fs，这个结构体是对进程当前所在目录的描述；也有指向files_struct结构体的指针files，这个结构体是对该进程已打开的所有文件进行描述。这里我们要注意进程在运行期间中可能处于不同的进程状态，例如：TASK_RUNNING/TASK_STOPPED/TASK_TRACED 等.</p>
<h2 id="fork调用"><a href="#fork调用" class="headerlink" title="fork调用"></a>fork调用</h2><p>在用户态下，使用fork()创建一个进程。除了这个函数，新进程的诞生还可以分别通过vfork()和clone()</p>
<ul>
<li>fork、vfork和clone三个API函数均由glibc库提供，它们分别在C库中封装了与其同名的系统调用fork()  </li>
<li>这几个函数调用对应不同场景，有些时候子进程需要拷贝父进程的整个地址空间，但是子进程创建后又立马去执行exec族函数造成效率低下。</li>
</ul>
<p>写时拷贝满足了这种需求，同时减少了地址空间复制带来的问题。<br>vfork 则是创建的子进程会完全共享父进程的地址空间，甚至是父进程的页表项，父子进程任意一方对任何数据的修改使得另一方都可以感知到。<br>clone函数创建子进程时灵活度比较大，因为它可以通过传递不同的参数来选择性的复制父进程的资源<br>系统调用fork、vfork和clone在内核中对应的服务例程分别为sys_fork()，sys_vfork()和sys_clone()。例如sys_fork()声明如下（arch/x86/kernel/process.c）：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">sys_fork</span><span class="params">(<span class="keyword">struct</span> pt_regs *regs)</span></span></div><div class="line">&#123;</div><div class="line">        <span class="keyword">return</span> do_fork(SIGCHLD, regs-&gt;sp, regs, <span class="number">0</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</div><div class="line">&#125;</div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">sys_vfork</span><span class="params">(<span class="keyword">struct</span> pt_regs *regs)</span></span></div><div class="line">&#123;</div><div class="line">        <span class="keyword">return</span> do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs-&gt;sp, regs, <span class="number">0</span>,</div><div class="line">                       <span class="literal">NULL</span>, <span class="literal">NULL</span>);</div><div class="line">&#125;</div><div class="line">sys_clone(<span class="keyword">unsigned</span> <span class="keyword">long</span> clone_flags, <span class="keyword">unsigned</span> <span class="keyword">long</span> newsp,</div><div class="line">          <span class="keyword">void</span> __user *parent_tid, <span class="keyword">void</span> __user *child_tid, <span class="keyword">struct</span> pt_regs *regs)</div><div class="line">&#123;</div><div class="line">        <span class="keyword">if</span> (!newsp)</div><div class="line">                newsp = regs-&gt;sp;</div><div class="line">        <span class="keyword">return</span> do_fork(clone_flags, newsp, regs, <span class="number">0</span>, parent_tid, child_tid);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>以看到do_fork()均被上述三个服务函数调用。do_fork()正是kernel创建进程的核心（）。通过分析调用过程如下，其中我分析的是最新版4.X Linux源码，在i386体系结构中，采取0x80中断调用syscall：</p>
<p><img src=""></p>
<p>从图中可以看到do_fork()和copy_process()是本文的主要分析对象。</p>
<p>do_fork函数的主要就是复制原来的进程成为另一个新的进程，在一开始，该函数定义了一个task_struct类型的指针p，用来接收即将为新进程（子进程）所分配的进程描述符。但是这个时候要检查clone_flags是否被跟踪就是ptrace，ptrace是用来标示一个进程是否被另外一个进程所跟踪。所谓跟踪，最常见的例子就是处于调试状态下的进程被debugger进程所跟踪。ptrace字段非0时说明debugger程序正在跟踪父进程，那么接下来通过fork_traceflag函数来检测子进程是否也要被跟踪。如果trace为1，那么就将跟踪标志CLONE_PTRACE加入标志变量clone_flags中。没有的话才可以进程创建，也就是copy_process()。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">long</span> _do_fork(<span class="keyword">unsigned</span> <span class="keyword">long</span> clone_flags,</div><div class="line">              <span class="keyword">unsigned</span> <span class="keyword">long</span> stack_start,</div><div class="line">              <span class="keyword">unsigned</span> <span class="keyword">long</span> stack_size,</div><div class="line">              <span class="keyword">int</span> __user *parent_tidptr,</div><div class="line">              <span class="keyword">int</span> __user *child_tidptr,</div><div class="line">              <span class="keyword">unsigned</span> <span class="keyword">long</span> tls)</div><div class="line">&#123;</div><div class="line">        <span class="keyword">struct</span> task_struct *p;</div><div class="line">        <span class="keyword">int</span> trace = <span class="number">0</span>;</div><div class="line">        <span class="keyword">long</span> nr;</div><div class="line">        <span class="keyword">if</span> (!(clone_flags &amp; CLONE_UNTRACED)) &#123;</div><div class="line">                <span class="keyword">if</span> (clone_flags &amp; CLONE_VFORK)</div><div class="line">                        trace = PTRACE_EVENT_VFORK;</div><div class="line">                <span class="keyword">else</span> <span class="keyword">if</span> ((clone_flags &amp; CSIGNAL) != SIGCHLD)</div><div class="line">                        trace = PTRACE_EVENT_CLONE;</div><div class="line">                <span class="keyword">else</span></div><div class="line">                        trace = PTRACE_EVENT_FORK;</div><div class="line">                <span class="keyword">if</span> (likely(!ptrace_event_enabled(current, trace)))</div><div class="line">                        trace = <span class="number">0</span>;</div><div class="line">        &#125;</div></pre></td></tr></table></figure>
<p>这条语句要做的是整个创建过程中最核心的工作：通过copy_process()创建子进程的描述符，并创建子进程执行时所需的其他数据结构，最终则会返回这个创建好的进程描述符。因为copy_process()函数过于巨大，所以另外开辟一篇文章讲解该函数实现。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">p = copy_process(clone_flags, stack_start, stack_size,</div><div class="line">                       child_tidptr, <span class="literal">NULL</span>, trace, tls);</div></pre></td></tr></table></figure>
<p>如果copy_process函数执行成功，那么将继续下面的代码。定义了一个完成量vfork,之后再对vfork完成量进行初始化。如果使用vfork系统调用来创建子进程，那么必然是子进程先执行。原因就是此处vfork完成量所起到的作用：当子进程调用exec函数或退出时就向父进程发出信号。此时，父进程才会被唤醒；否则一直等待。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">if</span> (!IS_ERR(p)) &#123;</div><div class="line">               <span class="keyword">struct</span> completion vfork;</div><div class="line">               <span class="keyword">struct</span> pid *pid;</div><div class="line"> </div><div class="line">               trace_sched_process_fork(current, p);</div><div class="line"> </div><div class="line">               pid = get_task_pid(p, PIDTYPE_PID);</div><div class="line">               nr = pid_vnr(pid);</div><div class="line">               <span class="keyword">if</span> (clone_flags &amp; CLONE_PARENT_SETTID)</div><div class="line">                    put_user(nr, parent_tidptr);</div><div class="line">               <span class="keyword">if</span> (clone_flags &amp; CLONE_VFORK) &#123;</div><div class="line">                    p-&gt;vfork_done = &amp;vfork;</div><div class="line">                       init_completion(&amp;vfork);</div><div class="line">                       get_task_struct(p);</div><div class="line">               &#125;</div></pre></td></tr></table></figure>
<p>下面通过wake_up_new_task函数使得父子进程之一优先运行；如果设置了ptrace，那么需要告诉跟踪器。如果CLONE_VFORK标志被设置，则通过wait操作将父进程阻塞，直至子进程调用exec函数或者退出。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line">wake_up_new_task(p);</div><div class="line"> </div><div class="line"><span class="comment">/* forking complete and child started to run, tell ptracer */</span></div><div class="line"><span class="keyword">if</span> (unlikely(trace))</div><div class="line">        ptrace_event_pid(trace, pid);</div><div class="line"><span class="keyword">if</span> (clone_flags &amp; CLONE_VFORK) &#123;</div><div class="line">    <span class="keyword">if</span> (!wait_for_vfork_done(p, &amp;vfork))</div><div class="line">               ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);</div><div class="line">&#125;</div><div class="line">put_pid(pid);</div><div class="line">如果copy_process()在执行的时候发生错误，则先释放已分配的pid；再根据PTR_ERR()的返回值得到错误代码，保存于pid中。 返回pid。这也就是为什么使用fork系统调用时父进程会返回子进程pid的原因。</div><div class="line"></div><div class="line"></div><div class="line">        &#125; <span class="keyword">else</span> &#123;</div><div class="line">                nr = PTR_ERR(p);</div><div class="line">        &#125;</div><div class="line">        <span class="keyword">return</span> nr;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="参考："><a href="#参考：" class="headerlink" title="参考："></a>参考：</h2><ul>
<li><a href="http://cs.lmu.edu/~ray/notes/linuxsyscalls/" target="_blank" rel="noopener">linuxsyscalls</a></li>
<li><a href="http://www.x86-64.org/documentation/abi.pdf" target="_blank" rel="noopener">abi</a></li>
<li><a href="http://www.tldp.org/LDP/tlk/ds/ds.html" target="_blank" rel="noopener">ds</a></li>
</ul>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://blog.mango16.cc/2017/09/24/fork系统调用分析/" data-id="cjrbtw20n00326d8hqv3otcqt"
         class="article-share-link">分享</a>
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/phper/">phper</a></li></ul>

    </footer>

  </div>

  
    
  <nav class="article-nav">
    
    
      <a href="/2017/06/07/OpenSource/" class="article-nav-link">
        <strong class="article-nav-caption">后一篇</strong>
        <div class="article-nav-title">开源许可证GPL、BSD、MIT、Mozilla、Apache和LGPL的区别</div>
      </a>
    
  </nav>


  

  
    
  <div class="gitalk" id="gitalk-container"></div>
  <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
  <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
  <script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js"></script>
  <script type="text/javascript">
      var gitalk = new Gitalk({
        clientID: 'caba8826a61790936307',
        clientSecret: '09a15037dcab30d9adf35a53ec6961939169f797',
        repo: 'm9rco.github.io',
        owner: 'm9rco',
        admin: ['m9rco'],
        // id: location.pathname,      // Ensure uniqueness and length less than 50
        id: md5(location.pathname),
        distractionFreeMode: false  // Facebook-like distraction free mode
      })

  gitalk.render('gitalk-container')
  </script>

  

</article>



</section>
  <footer class="footer">
  
  <div class="outer">
    <ul class="list-inline">
      <li>&copy; 2019 无敌小笼包</li>
      <i class="pulse"></i>
    </ul>
  </div>
</footer>

</main>
<aside class="sidebar">
  <button class="navbar-toggle"></button>

<nav class="navbar">
  
    <div class="logo">
      <a href="/"><img src="/images/hexo.svg" alt="无敌小笼包"></a>
    </div>
  
  <ul class="nav nav-main">
    
      <li class="nav-item">
        <a class="nav-item-link" href="/">主页</a>
      </li>
    
      <li class="nav-item">
        <a class="nav-item-link" href="/archives">归档</a>
      </li>
    
      <li class="nav-item">
        <a class="nav-item-link" href="/gallery">相册</a>
      </li>
    
      <li class="nav-item">
        <a class="nav-item-link" href="/nagging">叽咕</a>
      </li>
    
      <li class="nav-item">
        <a class="nav-item-link" href="/about">关于</a>
      </li>
    
    <li class="nav-item">
      <a class="nav-item-link nav-item-search" title="Search">
        <i class="fe fe-search"></i>
        搜索
      </a>
    </li>
  </ul>
</nav>

<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
        <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
          <i class="fe fe-feed"></i>
        </a>
      
    </li>
  </ul>
</nav>

<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
</aside>
  <script src="/js/jquery-2.0.3.min.js"></script>
<script src="/js/lazyload.min.js"></script>


  <script src="/fancybox/jquery.fancybox.min.js"></script>



  <script src="/js/search.js"></script>


<script src="/js/ocean.js"></script>

</body>
</html>